package com.puzzlebazar.client.main.control;

import java.util.HashMap;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.user.client.History;
import com.puzzlebazar.client.util.GWTUtil;

public abstract class HistoryControllerImpl extends RootControllerImpl 
	implements HistoryController {

	private int startPageIndex = -1;
	private int pageIndex = 0;
	private State switchingToState = null;
	
	private String previousStateToken = null;
	private HashMap<String,String> previousStateParams = null;
	private String currentStateToken = null;
	private HashMap<String,String> currentStateParams = null;
	
	private final HashMap<String, HistoryState> stateTokenMap = 
		new HashMap<String, HistoryState>();

	private boolean doNotReload = false;
	private String lastValidHistoryToken = null;
	private int lastValidPageIndex = -1;
	
	public HistoryControllerImpl() {
		super();
	    History.addValueChangeHandler( this );
	}
	
	@Override
	public void addHistoryToken(String historyToken, HistoryState state) {
		assert( stateTokenMap.get(historyToken) == null );
		stateTokenMap.put( historyToken, state );
	}
	
	/**
	 * Switches to a state given an history state token
	 * 
	 * @param historyToken The history state token to switch to
	 * @throws Exception
	 */
	private void switchToHistoryToken( String historyToken ) throws Exception {
		
		HashMap<String,String> params = GWTUtil.getHistoryTokenParameters(historyToken);
		String token = GWTUtil.getHistoryToken(historyToken);
		HistoryState forcedState = forcedState();
		HistoryState state = forcedState;
		if( state == null )
			state = stateTokenMap.get( token );
		
		if( state == null )
			throw new Exception( "Invalid history state token" );

		if( !state.canEnter() )
			History.back();
		
		if( params == null )
			params = new HashMap<String,String>();
		
		pageIndex = 0;
		try{
			pageIndex = Integer.parseInt( params.remove("i") );
			if( pageIndex < 0 ) pageIndex = 0;
		}
		catch( Exception exception ) {}
		if( startPageIndex == -1 || pageIndex < startPageIndex )
			startPageIndex = pageIndex;				
		
		if( forcedState != null ) {
			// Do not use parameters other than "i" for a forced state
			params.clear();
		}
		
		boolean canNavigate;
		if( state != null && state == currentState )
			canNavigate = confirmLeaveState();
		else
			canNavigate = leaveCurrentState();
		
		if( !canNavigate ) {
			doNotReload = true;
			// Try to guess if we should go forward or backward. 
			// In case we got it wrong, it will be catched later.
			if( pageIndex == lastValidPageIndex-1 )
				History.forward();
			else
				History.back();
			return;
		}
		lastValidHistoryToken = historyToken;
		lastValidPageIndex = pageIndex;
		previousStateToken = currentStateToken;
		previousStateParams = currentStateParams;
		currentStateToken = token;
		currentStateParams = new HashMap<String, String>(params);
		state.setParams( params );
		if( state != currentState ) {
			switchingToState = state;
			enterState( state );
			switchingToState = null;
		}
		pageIdUpdated();
	}

	@Override
	public void onValueChange(ValueChangeEvent<String> event) {
		String historyToken = event.getValue();		
		if( doNotReload ) {
			// In case we didn't land on the right page, reload the right page
			pageIndex = lastValidPageIndex;
			if( historyToken != lastValidHistoryToken )
				History.newItem( lastValidHistoryToken );				
			doNotReload = false;
			return;
		}
		
		if( historyToken.length() != 0 ) {
			try {
				switchToHistoryToken( historyToken );
			} catch (Exception exception) {
				exception.printStackTrace();
				pageIndex = startPageIndex-1;
				try {
					switchToHistoryToken( getDefaultStateToken() );
				} catch (Exception exception2) {
					exception2.printStackTrace();
				}
			}
		}
		else {
			pageIndex = startPageIndex-1;
			try {
				switchToHistoryToken( getDefaultStateToken() );
			} catch (Exception exception) {
				exception.printStackTrace();
			}
		}
	}

	@Override
	public void reloadCurrentState() {
		History.fireCurrentHistoryState();
	}

	@Override
	public void backToPreviousState() {
		if( pageIndex <= startPageIndex ) {
			gotoDefaultState();			
		}
		else
			History.back();
	}

	@Override
	public void forwardToPreviousState() {
		if( previousStateToken == null ) {
			gotoDefaultState();			
		}
		else {
			History.newItem( buildToken(previousStateToken, previousStateParams));
		}
	}
	
	@Override
	public void enterState(State state) {

		if( state != switchingToState && state instanceof HistoryState ) {
			HistoryState historyState = ((HistoryState)state);
			String stateToken = historyState.getStateToken();
			if( stateToken != null ) {
				// Will fire onHistoryChanged, and consequently super.switchToState()
				String token = buildToken( stateToken, historyState.getParams() );
				History.newItem( token );
				return;
			}
		}
		
		// Token cannot be processed via history, switch directly
		super.enterState( state );
	}

	@Override
	public String buildToken(String historyToken, HashMap<String, String> params) {
		
		String result = GWTUtil.buildHistoryToken(historyToken,params);
		if( params == null || params.isEmpty() )
			result += "?";
		else
			result += "&";
		result += "i=" + (pageIndex+1);
		return result;
	}

	/**
	 * Called whenever the page id has been updated. An updated pageID invalidates all tokens
	 * that were built with buildToken. Subclass should override this and rebuild all their
	 * tokens.
	 */
	protected void pageIdUpdated() {
	}

	/**
	 * This method can be used by subclasses to restrict state change and enforce that a transition
	 * occurs to a particular state
	 * 
	 * @return null if no state is forced, or the forced state otherwise
	 */
	protected HistoryState forcedState() {
		return null;
	}


}
